﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Xml.Serialization;

namespace ContinuumSL.Classes
{
    /*
     * Fund : This class acts as memory, storing flow over time of any one account. They are affected by actors 
     *  who have no memory of their own.
     * 
     * This might be a checking account, savings account, IRA, etc. which you want to shift funds into and out of.
     * 
     * A single fund might be acted upon by an unlimited number Actors.
     * (However an Actor can only refer to one or two funds, representing the 'from' and 'to' funds) 
     * 
     */

    // Spending : (such as Checking) are not counted toward value or debt
    // Savings : are counted towards value
    // Debt : are counted towards debt; are -not- counted toward value
    // DebtEquity : are counted towards both debt and value (equity)
    public enum FundClassEnum { SpendingAccount, Debt, DebtEquity, Savings }

    public class Fund
    {
        protected Balance _InceptionBalance = new Balance(0.0m, DateTime.Now.Date, "");
        [Description("The Initial Balance of the Fund")]
        public Balance InceptionBalance { get { return _InceptionBalance; } set { _InceptionBalance = value; } }

        protected Decimal _EquityOffset = 0.0m;
        [Description("This lets you adjust the equity above or below loan value")]
        public Decimal EquityOffset { get { return _EquityOffset; } set { _EquityOffset = value; } }

        protected string _Name = "FundName";
        [Description("The name which the Fund is referred to")]
        public string Name { get { return _Name; } set { _Name = value; } }

        protected FundClassEnum _FundClass = FundClassEnum.Savings;
        [Description("The class if fund used for group analysis")]
        public FundClassEnum FundClass { get { return _FundClass; } set { _FundClass = value; } }

        protected Boolean _IsBenchmark = false;
        [Description("Benchmark funds are pseudo funds which are not included in totals.")]
        public Boolean IsBenchmark { get { return _IsBenchmark; } set { _IsBenchmark = value; } }

        protected Boolean _IsHidden = false;
        [Description("Hidden funds are not shown in any of the graphs yet are still included in statistics.")]
        public Boolean IsHidden { get { return _IsHidden; } set { _IsHidden = value; } }

        protected List<Balance> _historicalBalances = new List<Balance>();
        [Description("Historical Balances")]
        public List<Balance> HistoricalBalances { get { return _historicalBalances; } set { _historicalBalances = value; } }

        [XmlIgnore]
        public List<Balance> VolatileBalances = new List<Balance>();
        protected bool IsSorted = false;

        [XmlIgnore]
        public List<Transaction> VolatileTransactions = new List<Transaction>();

        private Balance _InterimBalance = new Balance(0.0m, DateTime.MinValue, "");
        [XmlIgnore]
        public Balance InterimBalance
        {
            get
            {
                if (_InterimBalance.BalanceDate == DateTime.MinValue)
                {
                    _InterimBalance = new Balance(InceptionBalance.Amount, InceptionBalance.BalanceDate, "");
                    return _InterimBalance;
                }
                else
                {
                    return _InterimBalance;
                }
            }
        }

        public override string ToString()
        {
            return Name;
        }

        #region constructors

        public Fund()
        {
        }

        public Fund(Balance InceptionBalance, string Name, FundClassEnum FundClass, Boolean IsBenchmark)
        {
            this.InceptionBalance = InceptionBalance;
            this.Name = Name;
            this.FundClass = FundClass;
            this.IsBenchmark = IsBenchmark;
        }

        #endregion

        #region History Points

        public List<DateTime> HistoryPoints()
        {
            List<DateTime> histdates = new List<DateTime>();

            foreach (Balance bal in HistoricalBalances)
            {
                histdates.Add(bal.BalanceDate);
            }

            return histdates;
        }

        public List<DateTime> HistoryPoints(DateTime StartDate, DateTime EndDate)
        {
            List<DateTime> histdates = new List<DateTime>();

            foreach (Balance bal in HistoricalBalances)
            {
                if (bal.BalanceDate >= StartDate && bal.BalanceDate <= EndDate) histdates.Add(bal.BalanceDate);
            }

            return histdates;
        }

        #endregion

        #region Volatile Balance Points
        public List<DateTime> VolatileBalancePoints()
        {
            List<DateTime> volbaldates = new List<DateTime>();

            foreach (Balance bal in VolatileBalances)
            {
                volbaldates.Add(bal.BalanceDate);
            }

            return volbaldates;
        }

        public List<DateTime> VolatileBalancePoints(DateTime StartDate, DateTime EndDate)
        {
            List<DateTime> volbaldates = new List<DateTime>();

            foreach (Balance bal in VolatileBalances)
            {
                if (bal.BalanceDate >= StartDate && bal.BalanceDate <= EndDate) volbaldates.Add(bal.BalanceDate);
            }

            return volbaldates;
        }

        public Dictionary<string, Transaction> GroupTransactions(DateTime StartDate)
        {
            Dictionary<string, Transaction> groupedTransactions = new Dictionary<string, Transaction>();

            foreach (Transaction t in VolatileTransactions)
            {
                if (t.BalanceDate >= StartDate)
                {
                    Transaction groupedTransaction;

                    // Affecting Actor is null only on Start and End date pseudo transactions
                    if (t.AffectingActor != null)
                    {
                        try
                        {
                            groupedTransaction = groupedTransactions[t.AffectingActor.ActorName];
                            groupedTransaction.Amount += t.Amount;
                        }
                        catch (System.Collections.Generic.KeyNotFoundException)
                        {
                            // Make a copy so that underlying transaction isnt affected
                            Transaction newTrans = new Transaction(t.Amount, t.BalanceDate, t.Description, null);

                            groupedTransactions.Add(t.AffectingActor.ActorName, newTrans);
                        }
                    }
                }

            }

            return groupedTransactions;
        }

        #endregion

        #region Combined Balance Points

        // Used for analysis only 
        public List<Balance> CombinedBalances(bool SortAscending)
        {
            List<Balance> balances = new List<Balance>();

            balances.AddRange(HistoricalBalances);
            balances.AddRange(VolatileBalances);

            if (SortAscending) balances.Sort(new BalanceComparer());
            else balances.Sort(new BalanceDescendingComparer());

            return balances;
        }

        #endregion

        // Use this to post transaction and immediately render balance using InterimBalance
        public Balance PostVolatile(Decimal Amount, DateTime PostDate, string Name, Actor AffectingActor)
        {
            Amount = decimal.Round(Amount, 2);

            Transaction trans = new Transaction(( 
                (FundClass == FundClassEnum.Debt || FundClass == FundClassEnum.DebtEquity) ? -1 : 1) * Amount, 
                PostDate, Name, AffectingActor);

            VolatileTransactions.Add(trans);

            // Make a copy so underlying balance reference isnt affected
            Balance NewBalance = new Balance(InterimBalance.Amount + trans.Amount, PostDate, Name);
            _InterimBalance = NewBalance;

            VolatileBalances.Add(NewBalance);

            return NewBalance;
        }

        public List<Activity> ProjectActivities(DateTime StartDate, DateTime EndDate)
        {
            List<Activity> Activities = new List<Activity>();

            foreach(Balance bal in HistoricalBalances) 
            {
                if (bal.BalanceDate >= StartDate && bal.BalanceDate <= EndDate)
                {
                    Activities.Add(new Activity(bal.BalanceDate, ActivityTypeEnum.FundReconcile, this, bal));
                }
            }

            return Activities;
        }

        public Balance CalculateBalance(DateTime dt)
        {
            Balance latestbal = null;

            List<Balance> balances = CombinedBalances(true);

            foreach (Balance bal in balances)
            {
                if (bal.BalanceDate > dt) break;

                latestbal = bal;
            }

            if (latestbal == null) latestbal = InceptionBalance;
            
            return latestbal;
        }

        // Balances are rendered on Activity construction in ProjectActivities()
        // So this method only ensures the interimbalance is updated 
        public void ExecuteActivity(Activity activity)
        {
            _InterimBalance = activity.RenderedBalancePrimary;
        }

        public void ResetVolatile()
        {
            VolatileBalances.Clear();
            VolatileTransactions.Clear();
            _InterimBalance = new Balance(0.0m, DateTime.MinValue, "");
        }
    }
}
